/******************************************/
/************* GPStrac Client *************/
/******************************************/
/*
* FILE: gpsClient.c
* AUTHOR: Ram Subramanian and Nicolas Papin
* PROJECT: CS 3251 Project 2 - Professor Traynor
* DESCRIPTION: Application client code.
* PURPOSE: Defines the behavior of the GPStrac client and an interface for users.
*/

/* Included libraries */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "gpsTrac.h"
#include "gpsConn.h"

#define USAGE "Usage: gpsClient -u <username> [-i <ipv4_address>]\n<username>:\
Max 19 characters\n<ipv4_address>: localhost by default\n\n"

int sendMsg(int, clntReq_t, servResp_t *, const void *);
int getResponse(int, servResp_t *);
void printInstructions();
int requestUpdate(int sock);
int requestLocations(int sock);
int requestHistory(int sock);
int requestAddFriend(int sock);
int requestFriends(int sock);
void printGPS(gpsCoord_t gps);

/* The main function */
int main(int argc, char *argv[])
{
    int sock, nerr = 0; /* The socket and a variable to test for errors. */
    struct sockaddr_in serv_addr;   /* The server address */
    char ipAddr[16];
    char client_id[USER_ID_LEN];

    /* Argument parsing */
    if (argc != 5 && argc != 3)
        dieWithMessage(USAGE);

    int i = 1, j = 0;
    strcpy(ipAddr, "127.0.0.1");

    while (i < argc)
    {
        if(!strcmp(argv[i], "-u"))
        {
            /* client_id = (clntID_t) atoi(argv[i + 1]);
            if (client_id < 1) dieWithMessage("Invalid client ID"); */
            //In case ID is a string
            strncpy(client_id, argv[i + 1], USER_ID_LEN - 1);
            client_id[USER_ID_LEN - 1] = (char) 0;
            nerr = 1;
        }
        else if(!strcmp(argv[i], "-i"))
        {
            if(inet_aton(argv[i + 1], &serv_addr.sin_addr))
                strcpy(ipAddr, argv[i + 1]);
            else dieWithMessage("Invalid IPv4 address");
        }
        else dieWithMessage(USAGE);

        i += 2;
    }

    if (!nerr) dieWithMessage(USAGE);

    /* Create the socket */
    if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        dieWithMessage("Can not create a socket");

    /* Initialize the server address structure. */
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = PF_INET;
    inet_aton(ipAddr, &serv_addr.sin_addr);
    //serv_addr.sin_addr.s_addr = inet_addr(ipAddr);
    serv_addr.sin_port = htons(SERV_PORT);

    /* Connect to server */
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) < 0)
        dieWithMessage("Can not connect to server");

    printf("Connected to server at %s on port %d\n", inet_ntoa(*(struct in_addr *)&serv_addr.sin_addr.s_addr),
                                                     ntohs(serv_addr.sin_port));

    int res;
    clntReq_t request;
    servResp_t response;

    /* Loop until an ID not logged in is entered */
	request.cmd = LOG_IN;
    request.msgLen = USER_ID_LEN;
    while((res = sendMsg(sock, request, &response, client_id)) > 0)
    {


        char temp[USER_ID_LEN];
        printf("%s is currently logged in. Please choose another id: ", client_id);
        /* scanf("%d", &temp); */
        /* fgets(temp, USER_ID_LEN, stdin); */
		scanf("%19s", temp);
		while(getchar() != '\n');
        strcpy(client_id, temp);
        request.cmd = LOG_IN;
		request.msgLen = USER_ID_LEN;
    }

    if(res < 0)
        dieWithMessage("Connection lost while attempting to authenticate with server. Shutting down.");

    printf("Successfully logged in as user: %s\n", client_id);

    /*
    * Do the user interfacing stuff here.
    * We will need to check at all times if the server disconnects on us.
    * We also need to give good information concerning the server's response here.
    */
    int done = 0, ch;

    printInstructions();
    do
    {
		ch = 0;
		response.retLen = 0;
		response.resp = REQ_INVALID;
		res = 0;
        /*
        * Give the user all of the functional options.
        * Upon selection of leave, we can break and handle those messages outside of the loop.
        */
        printf("Command (0 for list): ");
        scanf("%d", &ch);

        while(getchar() != '\n');

        switch (ch)
        {
            case 0:
                printInstructions();
                break;
			case 1:
                res = requestUpdate(sock);
                break;
			case 2:
                printf("Functionality not yet implemented\n");
                break;
			case 3:
                res = requestLocations(sock);
                break;
			case 4:
                res = requestHistory(sock);
                break;
			case 5:
                res = requestAddFriend(sock);
                break;
            case 6:
                res = requestFriends(sock);
                break;
			case 7:
                request.cmd = LEAVE;
                request.msgLen = 0;
                sendMsg(sock, request, &response, NULL);
                done = 1;
                break;
			default:
                printf("Invalid option\n");
                continue;
	   }

	   if(!res)
	   {
	       printf("Request completed successfully.\n");
	   }
       else if(res == 1)
	   {
	       printf("Server was unable to complete the request. Please reattempt your request.\nIf this problem repeats, it may be best to restart the application.\n");
	   }
	   else if(res == -1)
	   {
	       printf("It appears that there is an error with your connection to the server.  Attempting to reconnect.\n");
	       close(sock); //Close the connection

	       //Attempt reconnect
	       if(connect(sock, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) < 0)
               dieWithMessage("Unable to reconnect with server. Shutting down");
           else
           {
               printf("Successfully restablished connection with server.\n");

               request.cmd = LOG_IN;
               request.msgLen = USER_ID_LEN;
               if(sendMsg(sock, request, &response, client_id))
                    dieWithMessage("Attempt to log back in failed.  Shutting down");
           }
	   }

    } while(!done);

    printf("Terminating connection with server and shutting down.\n");
    close(sock);
}

void printInstructions()
{
    printf("Server accepts the following commands:\n");
    printf("1. Update your location\n");
    printf("2. Get list of online users\n");
    printf("3. Get user locations\n");
    printf("4. Get the list of locations a user has been\n");
    printf("5. Add a friend\n");
    printf("6. View friends list\n");
    printf("7. Exit\n");
}

int requestUpdate(int sock)
{
    int ret;
    clntReq_t request;
    servResp_t response;
    gpsCoord_t myLoc;
    float lat, lon;
    char NS, EW;

    printf("latitude N/S longitude E/W (Eg. 2.73 N 9.001 W): ");
    scanf("%f %c %f %c", &lat, &NS, &lon, &EW);

    while(getchar() != '\n');

    if (lat > 90 || lat < 0 || lon > 180 || lon < 0 || (NS != 'N' && NS != 'S') || (EW != 'E' && EW != 'W'))
    {
        printf("Invalid co-ordinates\n");
        return;
    }

    myLoc.inval = 0;
    myLoc.lat = (int)(lat * 3600);
    myLoc.lat *= (NS == 'N')?1:-1;
    myLoc.lon = (int)(lon * 3600);
    myLoc.lon *= (EW == 'E')?1:-1;
    htonGPS(&myLoc);

    request.cmd = UPDATE_LOC;
    request.msgLen = sizeof(myLoc);
    if(ret = sendMsg(sock, request, &response, &myLoc))
        return ret;

    return 0;
}

int requestLocations(int sock)
{
    clntReq_t request;
    servResp_t response;
    int currNum = 32, i = 0, j = 0, ret;
    char (*userBuffer)[USER_ID_LEN];
    userBuffer = malloc(currNum * sizeof(*userBuffer));
    char currName[USER_ID_LEN];
    char *tmp;
    char c;
    if(userBuffer == NULL)
    {
        close(sock);
        dieWithMessage("Memory Allocation Error - Exiting");
    }

    printf("Enter usernames seperated by a space: ");
    do
    {
        scanf("%19s", &userBuffer[i]);

        i++;
        if(i >= currNum)
        {
            currNum *= 2;
            tmp = realloc(userBuffer, currNum * sizeof(*userBuffer));

            if(tmp == NULL)
            {
                free(userBuffer);
                close(sock);
                dieWithMessage("Memory Allocation Error - Exiting");
            }
            else
                userBuffer = tmp;
        }

        c = getchar();
        while(c != ' ' && c != '\n')
            c = getchar();
        if (c == '\n')
            c = (char) 0;
    }while(c != (char) 0);

    //userBuffer[20][USER_ID_LEN] = (char) 0;
    //if(c) while(getchar() != '\n');

    request.cmd = VIEW_LOC;
    request.msgLen = USER_ID_LEN * i;

    if(ret = sendMsg(sock, request, &response, userBuffer))
        return ret;

    if(response.retLen != sizeof(gpsCoord_t) * i)
    {
        close(sock);
        dieWithMessage("Request and Reponse length mis-match");
    }

    gpsCoord_t userLoc[i];

    if(receiveAmount(sock, userLoc, response.retLen))
        return -1;

    printf("Locations of friends: \n");
    while (j < i)
    {
        ntohGPS(&userLoc[j]);

        printf("%s -> ", userBuffer[j]);//userBuffer[j * USER_ID_LEN]);
        printGPS(userLoc[j]);

        j ++;
    }

    free(userBuffer);

    return 0;
}

int requestHistory(int sock)
{
    int ret;
    servResp_t response;
    clntReq_t request;
    char userName[USER_ID_LEN];

    printf("Enter username: ");
    scanf("%19s", userName);
    while(getchar() != '\n');

    request.cmd = LOC_HIST;
    request.msgLen = sizeof(userName);

    if(ret = sendMsg(sock, request, &response, userName))
        return ret;

    int respNum = response.retLen / sizeof(gpsCoord_t);
    gpsCoord_t userLoc[response.retLen / sizeof(gpsCoord_t)];

    if(receiveAmount(sock, userLoc, response.retLen))
        return -1;

    int i = 0;

    printf("User %s History:\n", userName);
    while (i < respNum)
    {
        ntohGPS(&userLoc[i]);
        printGPS(userLoc[i]);

        i++;
    }

    return 0;
}

int requestAddFriend(int sock)
{
    int ret;
    clntReq_t request;
    servResp_t response;

    char userName[USER_ID_LEN];

    printf("Enter username: ");
    scanf("%19s", userName);
    while(getchar() != '\n');

    request.cmd = ADD_FRIEND;
    request.msgLen = sizeof(userName);

    if(ret = sendMsg(sock, request, &response, userName))
        return ret;

    return 0;
}

int requestFriends(int sock)
{
    int ret;
    clntReq_t request;
    servResp_t response;

    request.cmd = GET_FRIENDS;
    request.msgLen = 0;

    if(ret = sendMsg(sock, request, &response, NULL))
        return ret;

    int numNames = response.retLen / USER_ID_LEN;
    printf("Resp len: %d\n", response.retLen);
    printf("Number of friends: %d\n", numNames);
    char friends[numNames][USER_ID_LEN];

    if(receiveAmount(sock, friends, response.retLen))
        return -1;

    printf("Friends:\n");

    int i = 0;
    while(i < numNames)
    {
        printf("%s\n", friends[i]);
        i++;
    }

    return 0;
}

// Returns 0 on success, -1 on connection failure, 1 on other request failure.
int sendMsg(int sock, clntReq_t request, servResp_t* response, const void *msg)
{
	htonRequest(&request);

    /* Send username */
    if(sendAmount(sock, &request, sizeof(clntReq_t)))
        return -1;

    ntohRequest(&request);

    /* Get acknowledgement */
    if(getResponse(sock, response))
        return -1;

    switch(response->resp)
    {
        case REQ_OK:
            if(msg == NULL)
                return 0;

            if(!sendAmount(sock, msg, request.msgLen))
                if(!getResponse(sock, response))
                    if(response->resp == REQ_OK)
                        return 0;
                    else
                        return 1;
            break;

        default:
            //printf("Request failed with response code %d.", response->resp);
            return 1;

    }

    //If any of the above fail.
    return -1;
}

int getResponse(int sock, servResp_t* response)
{
    /* Get acknowledgement */
    if(receiveAmount(sock, response, sizeof(servResp_t)))
        return -1;

    ntohResponse(response);

    return 0;
}

void printGPS(gpsCoord_t gps)
{
    char NS = 'N', EW = 'E';
    float lat, lon;

    if(!gps.inval)
    {
        if(gps.lat < 0)
        {
            NS = 'S';
            gps.lat *= -1;
        }

        if(gps.lon < 0)
        {
            EW = 'W';
            gps.lon *= -1;
        }

        lat = gps.lat / 3600.0f;
        lon = gps.lon / 3600.0f;

        printf("%f %c %f %c\n", lat, NS, lon, EW);
    }
    else
    {
       printf("Invalid\n");
    }
}
